// Copyright (c) 2009 Mike Haight, Andy Kipp
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.

#include "Engine.h"

using namespace HandBanana::Core;
using namespace HandBanana::Game;

Engine::Engine(void)
{
    // Initialize Flags
    {
        Logger::Info(L"Initializing Flags");
        QuitFlag = false;
        mLoading = false;
        mRunning = false;
        mStallRendering = false;
    }

    // Initialize Window
    {
        Logger::Info(L"Initializing Window");
        mMainWindow = boost::shared_ptr<Window>(new Window(L"Handbanana", HandBanana::Core::Rectangle(50, 50, 512, 512)));
    }

    // Initialize Timer
    {
        Logger::Info(L"Initializing Timer");
        mTimer = boost::shared_ptr<Timer>(new Timer(INVERSE_TICKS_PER_SECOND));
    }

    // Initialize the input manager
    {

        Logger::Info(L"Initializing Input.");
        mInput = boost::shared_ptr<Input>(new Input(mMainWindow));
        mInput->Acquire();
    }

    // Initialize the Renderer
    {
        Logger::Info(L"Initializing Renderer.");
        mRenderer = boost::shared_ptr<Renderer>(new Renderer(mMainWindow));
    }

    // Initialize Views
    {
        mWorldView = Rectangle(0, 0, 512, 512);
        mUIView = Rectangle(0, 0, 512, 512);
        //Logger::Info(L"Initializing World View: {0}", mWorldView.ToString());
        //Logger::Info(L"Initializing UI View: {0}", mUIView.ToString());
    }

    // Initialize Loading Bar
    {
        Logger::Info(L"Initializing Loading Bar.");
        //mLoadingBar = gcnew LoadingBar();
        //mLoadingBar->Location = Coordinates::Location(128.0f, 400.0f);
    }
}


boost::shared_ptr<Renderer> Engine::GetRenderer(void) const
{
    return mRenderer;
}


boost::shared_ptr<Scene> Engine::GetScene(void) const
{
    return mScene;
}


void Engine::Start(void)
{
    if (!mRunning)
    {
        // Start the Renderer
        {
            Logger::Info(L"Starting Renderer.");
            mRenderer->Start();
        }

        // Start the Engine thread
        {
            Logger::Info(L"Starting Engine Thread.");

            // Set state to running
            mRunning = true;
            mEngineThread = boost::shared_ptr<boost::thread>(new boost::thread(boost::bind(&Engine::Run, this)));
        }

        // Start the Loading Thread
        {
            Logger::Info(L"Starting Load Thread.");

            // Set state to running
            mLoading = true;
            mLoadThread = boost::shared_ptr<boost::thread>(new boost::thread(boost::bind(&Engine::Load, this)));
        }

        // Initialize the window events after all other objects have been initialized
        {
            mMainWindow->Activated +=   boost::shared_ptr<MemberDelegate<Engine,EventArgs>>(new MemberDelegate<Engine,EventArgs>(this, &Engine::WindowActivated));
            mMainWindow->Closed +=      boost::shared_ptr<MemberDelegate<Engine,EventArgs>>(new MemberDelegate<Engine,EventArgs>(this, &Engine::WindowClosed));
            mMainWindow->Deactivated += boost::shared_ptr<MemberDelegate<Engine,EventArgs>>(new MemberDelegate<Engine,EventArgs>(this, &Engine::WindowDeactivated));
            mMainWindow->KillFocus +=   boost::shared_ptr<MemberDelegate<Engine,EventArgs>>(new MemberDelegate<Engine,EventArgs>(this, &Engine::WindowKillFocus));
            mMainWindow->Minimized +=   boost::shared_ptr<MemberDelegate<Engine,EventArgs>>(new MemberDelegate<Engine,EventArgs>(this, &Engine::WindowMinimized));
            mMainWindow->Moved +=       boost::shared_ptr<MemberDelegate<Engine,MoveEventArgs>>(new MemberDelegate<Engine,MoveEventArgs>(this, &Engine::WindowMoved));
            mMainWindow->Restored +=    boost::shared_ptr<MemberDelegate<Engine,EventArgs>>(new MemberDelegate<Engine,EventArgs>(this, &Engine::WindowRestored));
            mMainWindow->SetFocus +=    boost::shared_ptr<MemberDelegate<Engine,EventArgs>>(new MemberDelegate<Engine,EventArgs>(this, &Engine::WindowSetFocus));
            mMainWindow->Sized +=       boost::shared_ptr<MemberDelegate<Engine,SizeEventArgs>>(new MemberDelegate<Engine,SizeEventArgs>(this, &Engine::WindowSized));
        }
    }
}


void Engine::Stop(void)
{
    if (mRunning)
    {
        // Clear all of the window events
        {
            mMainWindow->Activated.Clear();
            mMainWindow->Closed.Clear();
            mMainWindow->Deactivated.Clear();
            mMainWindow->KillFocus.Clear();
            mMainWindow->Minimized.Clear();
            mMainWindow->Moved.Clear();
            mMainWindow->Restored.Clear();
            mMainWindow->SetFocus.Clear();
            mMainWindow->Sized.Clear();
        }

        // Set state to not running
        {
            Logger::Info(L"Stopping Engine.");

            // Join the render thread
            mRunning = false;
            mEngineThread->join();
        }

        // Stop the renderer
        {
            Logger::Info(L"Stopping renderer.");
            mRenderer->Stop();
        }

        // Unacquire the input
        {
            Logger::Info(L"Unacquiring input.");
            mInput->Unacquire();
        }
    }
}


void Engine::Run(void)
{
    while (mRunning)
    {
        if (mStallRendering)
        {
            continue;
        }

        // Start timing
        mTimer->BeginTiming();

        // See if we have recieved a tick
        if (mTimer->Tick())
        {
            // Clear Renderer to black
            mRenderer->Clear();

            if (!mLoading)
            {
                // Update
                Update();

                // Render
                Render();
            } 
            else
            {
                // Update Loading Bar
                //mLoadingBar->Update();

                // Draw Loading Bar
                mRenderer->SetView(mUIView);
                {
                    std::vector<Sprite*> spriteList;
                    mRenderer->BeginDrawing();
                    //mLoadingBar->GetSprites(spriteList);
                    mRenderer->Draw(spriteList);
                    mRenderer->EndDrawing();
                }
            }

            // Reset the timer
            mTimer->Reset();
        }

        // Present this frame
        mRenderer->Present();

        // End timing this frame
        mTimer->EndTiming();
    }

    //ResourceManager::ReleaseTextures();
}


void Engine::Load(void)
{
    // Load Scene
    {
        Logger::Info(L"Loading Scene.");
        mScene =   boost::shared_ptr<Scene>(Scene::Load(L"data/scene/World.xml"));
    }

    /*
    mLoadingBar->SetValue(0.1f);

    // Initialize ResourceManager
    {
        smLogger->Info("Initializing Resource Manager.");
        ResourceManager::Initialize();
    }

    mLoadingBar->SetValue(0.2f);

    // Initialize Scripting Engine
    {
        smLogger->Info("Initiailzing Scripting Engine.");
        ScriptingEngine::Initialize();
    }

    mLoadingBar->SetValue(0.3f);

    // Load Objects
    {
        ResourceManager::LoadObjects("data/monster/Monsters.xml");
    }

    mLoadingBar->SetValue(0.4f);

    // Load User Interface
    {
        smLogger->Info("Initializing User Interface.");
        smUserInterface = gcnew UI();
    }

    mLoadingBar->SetValue(0.5f);



    mLoadingBar->SetValue(0.8f);

    // Load Player
    {
        smLogger->Info("Initializing Player.");
        smPlayer = gcnew Player();
        smPlayer->Location = Coordinates::Location(512/2, 512/2);
    }

    smLoadingBar->SetValue(0.9f);

    // Load Smoke Particle System
    {
        smSmokeParticleSystem = gcnew ParticleSystem<SmokeParticle^>(SmokeParticle::smDefaultSettings);
        smSmokeParticleSystem->Origin = Coordinates::Location(256, 256);
        smSmokeParticleSystem->Regenerate = true;
    }

    // Load Fire Particle System
    {
        smFireParticleSystem = gcnew ParticleSystem<FireParticle^>(FireParticle::smDefaultSettings);
        smFireParticleSystem->Origin = Coordinates::Location(384, 256);
        smFireParticleSystem->Regenerate = true;
    }

    // Load Aura Particle System
    {
        smAuraParticleSystem = gcnew ParticleSystem<AuraParticle^>(AuraParticle::smDefaultSettings);
        smAuraParticleSystem->Origin = Coordinates::Location(smPlayer->Location.X + (smPlayer->Size.Width / 2.0f) - 8, smPlayer->Location.Y + (smPlayer->Size.Height / 2.0f));
        smAuraParticleSystem->Regenerate = true;
    }

    smLoadingBar->SetValue(1.0f);

    // Temporary for now
    smAuraParticleSystem->Begin();
    smFireParticleSystem->Begin();
    smSmokeParticleSystem->Begin();
    */

    Logger::Info(L"Loading Thread exiting.");

    // Finish Loading
    mLoading = false;
}


void Engine::Update(void)
{
    // Process input
    {
        // Update our key states
        mInput->Update();

        // For now, escape key exits
        if (mInput->KeyDownState(DIK_ESCAPE))
        {
            QuitFlag = true;
        }

        // Reset Actions
        /*
        smPlayer->Actions.MoveLeft = false;
        smPlayer->Actions.MoveRight = false;
        smPlayer->Actions.MoveUp = false;
        smPlayer->Actions.MoveDown = false;
        smPlayer->Actions.Attack = false;
        */

        // Hardcoded key values for now
        if (mInput->KeyPressedState(DIK_LEFT))
        {
            //smPlayer->Actions.MoveLeft = true;
        }
        else if (mInput->KeyPressedState(DIK_RIGHT))
        {
            //smPlayer->Actions.MoveRight = true;
        }

        if (mInput->KeyPressedState(DIK_UP))
        {
            //smPlayer->Actions.MoveUp = true;
        }
        else if (mInput->KeyPressedState(DIK_DOWN))
        {
            //smPlayer->Actions.MoveDown = true;
        }

        // Test the health and magic bars
        static float health = 0;
        if (mInput->KeyPressedState(DIK_Q))
        {
            //smUserInterface->HealthBar->SetValue(health += 0.01f);
        }
        else if (mInput->KeyPressedState(DIK_W))
        {
            //smUserInterface->HealthBar->SetValue(health -= 0.01f);
        }

        // Test the health and magic bars
        static float magic = 0;
        if (mInput->KeyPressedState(DIK_A))
        {
            //smUserInterface->MagicBar->SetValue(magic += 0.01f);
        }
        else if (mInput->KeyPressedState(DIK_S))
        {
            //smUserInterface->MagicBar->SetValue(magic -= 0.01f);
        }
    }

    /*
    // Update World
    {
        // Do nothing right now
    }

    // Update Player
    {
        smPlayer->Update();
    }

    // Update User Interface
    {
        smUserInterface->Update();
    }

    // Update Particle System(s)
    {
        // Move Aura with Player
        smAuraParticleSystem->Origin = Coordinates::Location(smPlayer->Location.X + (smPlayer->Size.Width / 2.0f) - 8, smPlayer->Location.Y + (smPlayer->Size.Height / 2.0f));

        smAuraParticleSystem->Update();
        smSmokeParticleSystem->Update();
        smFireParticleSystem->Update();
    }

    // Update World View
    {
        if (smPlayer->Location.X + (smWorldView.Width / 2) > smWorld->Size.Width) 
        {
            smWorldView.X = smWorld->Size.Width - smWorldView.Width;
        }
        else if (smPlayer->Location.X - (smWorldView.Width / 2) > 0)
        {
            smWorldView.X = smPlayer->Location.X - (smWorldView.Width / 2);
        }
        else
        {
            smWorldView.X = 0;
        }

        if (smPlayer->Location.Y + (smWorldView.Height / 2) > smWorld->Size.Height)
        {
            smWorldView.Y = smWorld->Size.Height - smWorldView.Height;
        }
        else if (smPlayer->Location.Y - (smWorldView.Height / 2) > 0)
        {
            smWorldView.Y = smPlayer->Location.Y - (smWorldView.Height / 2);
        }
        else
        {
            smWorldView.Y = 0;
        }
    }
    */
}


void Engine::Render(void)
{
    // Begin Drawing
    {
        mRenderer->BeginDrawing();
    }

    // Draw the sprites in the world
    mRenderer->SetView(mWorldView);
    {
        std::vector<Sprite*> worldSprites;

        /*
        // Draw Background Layer
        {
            // Get Layer WorldObjects in WorldView Rectangle
            std::vector<WorldObject*> objectList = smWorld->Layers["Background"]->GetObjects(smWorldView);
            for each (WorldObject^ object in objectList)
                object->GetSprites(%worldSprites);
        }

        // Draw Objects in World
        {
            // Get World Objects in WorldView Rectangle
            List<WorldObject^>^ objectList = smWorld->GetObjects(smWorldView);
            for each (WorldObject^ object in objectList)
                object->GetSprites(%worldSprites);
        }

        // Draw Player
         
        {
            smPlayer->GetSprites(%worldSprites);
        }

        // Draw Particle System(s)
        {
            smSmokeParticleSystem->GetSprites(%worldSprites);
            smFireParticleSystem->GetSprites(%worldSprites);
            smAuraParticleSystem->GetSprites(%worldSprites);
        }

        // Draw Foreground Layer
        {

        }
        */

        // Draw Sprite List
        mRenderer->Draw(worldSprites);
    }

    // Draw UI
    mRenderer->SetView(mUIView);
    {
        std::vector<Sprite*> uiSprites;
        //mUserInterface->GetSprites(uiSprites);
        mRenderer->Draw(uiSprites);
    }

    // End Drawing
    {
        mRenderer->EndDrawing();
    }
}


void Engine::WindowActivated(Object& argSender, EventArgs& e)
{
}


void Engine::WindowClosed(Object& argSender, EventArgs& e)
{
    Engine::QuitFlag = true;
}


void Engine::WindowDeactivated(Object& argSender, EventArgs& e)
{
}


void Engine::WindowKillFocus(Object& argSender, EventArgs& e)
{
    mInput->Unacquire();
    Logger::Info(L"KillFocus, unacquire input");
}


void Engine::WindowMinimized(Object& argSender, EventArgs& e)
{
    mStallRendering = true;
}


void Engine::WindowMoved(Object& argSender, MoveEventArgs& e)
{
}


void Engine::WindowRestored(Object& argSender, EventArgs& e)
{
    mStallRendering = false;
}


void Engine::WindowSetFocus(Object& argSender, EventArgs& e)
{
    mInput->Acquire();
    Logger::Info(L"SetFocus, acquire input");
}


void Engine::WindowSized(Object& argSender, SizeEventArgs& e)
{
}
